﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using XcoAppSpaces.Transport.Sockets.Auth;
using XcoAppSpaces.Transport.Sockets.Auth.Contract;
using System.Threading.Tasks;
using System.Threading;

namespace test.XcoAppSpaces.Transport.Sockets.Auth
{
	[TestFixture]
	public class DoHandshakeIntegrationTests
	{

		[Test]
		public void ClientServerHandshakeInteraction_Successful()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			var clientSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, true);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_DifferentGreeting()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			var clientSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "bye", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_DifferentGreeting2()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "bye", SecondsAfterInvalidConnectionIsClosed = 5 };
			var clientSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_DifferentKey()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			var clientSettings = new SecuritySettings { AuthenticationKey = "1234", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_DifferentChallengeLength()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			var clientSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 12, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);
		}

		[Test]
		public void ClientServerHandshakeInteraction_Fail_DifferentChallengeLength2()
		{
			var serverSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 12, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			var clientSettings = new SecuritySettings { AuthenticationKey = "test", ChallengeLength = 10, Greeting = "hello", SecondsAfterInvalidConnectionIsClosed = 5 };
			ClientServerHandshakeInteraction(serverSettings, clientSettings, false);
		}

		
		public void ClientServerHandshakeInteraction(SecuritySettings serverSettings, SecuritySettings clientSettings, bool shouldBeValid)
		{
			using (var connections = new ConnectedTcpClientPair())
			{
				var serverHandshake = new DoServerHandshake(new PrepareAuthentication(), new ReceiveGreeting(), new SendChallenge(), new ReceiveResponse(), new FinishAuthentication());
				var clientHandshake = new DoClientHandshake(new PrepareAuthentication(), new SendGreeting(), new ReceiveChallenge(), new SendResponse(), new FinishAuthentication());
				serverHandshake.Inject(serverSettings);
				clientHandshake.Inject(clientSettings);

				bool serverValid = false;
				bool serverInvalid = false;
				bool clientValid = false;
				bool clientInvalid = false;

				serverHandshake.Out_AuthenticatedConnection += _ => serverValid = true; //Console.WriteLine("Server connection valid!");
				serverHandshake.Out_InvalidConnection += _ => serverInvalid = true; //Console.WriteLine("Server connection invalid!");
				clientHandshake.Out_AuthenticatedConnection += _ => clientValid = true; //Console.WriteLine("Client connection valid!");
				clientHandshake.Out_InvalidConnection += _ => clientInvalid = true; //Console.WriteLine("Client connection invalid!");

				Parallel.Invoke(
					() => clientHandshake.In_Connection(connections.SendingClient),
					() => serverHandshake.In_Connection(connections.ReceivingClient));

				Assert.IsTrue(SpinWait.SpinUntil(() => (serverValid && clientValid) || (serverInvalid && clientInvalid), 10000));
				if (shouldBeValid)
					Assert.IsTrue(serverValid && clientValid, "Server and client should be valid!");
				else
					Assert.IsTrue(serverInvalid && clientInvalid, "Server and client should be invalid!");
				
			}
		}
	}
}
